/* Copyright 2003 Marcin Siennicki <m.siennicki@cloos.pl>
 * see COPYING file for details */

#include <cstdio>

#include <cstring>
#include <termios.h>
#include <sys/types.h>
#include <fcntl.h>
#include <cstdlib>
#include <csignal>
#include <unistd.h>

#include "../include/cssl.h"

/*
 * Static variables and constants
 */

/* signal number for serial i/o read */
static int CSSL_SIGNAL = 0;

/* boolean that say if we have started cssl */
static int cssl_started = 0;

/* sigactions */
static struct sigaction sa;
static struct sigaction oldsa;

/* head of the cssl_t list */
static cssl_t *head = nullptr;

/* error messages table */
static const char *cssl_errors[] = {
        "cssl: OK",
        "cssl: there's no free signal",
        "cssl: not started",
        "cssl: null pointer",
        "cssl: oops",
        "cssl: out of memory",
        "cssl: cannot open file"
};

/* status of last cssl function */
static cssl_error_t cssl_error = CSSL_OK;

/* prototype of signal handler */
static void cssl_handler(int signo, siginfo_t *info, void *ignored);


/**************************************
 * Public functions
 **************************************/

/*-------------------------------------
 * Error handling
 */

/* gets the last operation status message */
const char *cssl_geterrormsg() {
    return cssl_errors[cssl_error];
}


/* gets the last error code */
int cssl_geterror() {
    return cssl_error;
}

/*-------------------------------------
 * Startig/stoping cssl
 */

/* starts cssl */
void cssl_start() {
    int sig;

    if (cssl_started) {
        return;
    }

    /* Here we scan for unused real time signal */
    sig = SIGRTMIN;

    do {

        /* get old sigaction */
        sigaction(sig, nullptr, &oldsa);

        /* if signal's handler is empty */
        if (oldsa.sa_handler == nullptr) {
            /* set the signal handler, and others */
            CSSL_SIGNAL = sig;
            sa.sa_sigaction = cssl_handler;
            sa.sa_flags = SA_SIGINFO;
            sa.sa_restorer = nullptr;
            sigemptyset(&sa.sa_mask);
            sigaction(CSSL_SIGNAL, &sa, nullptr);

            /* OK, the cssl is started */
            cssl_started = 1;
            cssl_error = CSSL_OK;
            return;
        } else {
            /* signal handler was not empty,
               restore original */
            sigaction(CSSL_SIGNAL, &oldsa, nullptr);
        }
        sig++;
    } while (sig <= SIGRTMAX);


    /* Sorry, there's no free signal */
    cssl_error = CSSL_ERROR_NOSIGNAL;

}

/* stops the cssl */
void cssl_stop() {
    /* if not started we do nothing */
    if (!cssl_started)
        return;

    /* we close all ports, and free the list */
    while (head)
        cssl_close(head);

    /* then we remove the signal handler */
    sigaction(CSSL_SIGNAL, &oldsa, nullptr);

    /* And at least : */
    cssl_started = 0;
    cssl_error = CSSL_OK;
}

/*-------------------------------------
 * Basic port operation - open/close
 */


/* opens the port */
cssl_t *cssl_open(const char *fname,
                  cssl_callback_t callback,
                  int id,
                  int baud,
                  int bits,
                  int parity,
                  int stop) {
    cssl_t *serial;

    if (!cssl_started) {
        cssl_error = CSSL_ERROR_NOTSTARTED;
        return nullptr;
    }

    /* create new cssl_t structure */
    serial = (cssl_t *) calloc(1, sizeof(cssl_t));

    /* oops, no memory */
    if (!serial) {
        cssl_error = CSSL_ERROR_MEMORY;
        return nullptr;
    }

    /* opening the file */
    if (callback) {
        /* user wants event driven reading */
        serial->fd = open(fname, O_RDWR | O_NOCTTY | O_NONBLOCK);
        fcntl(serial->fd, __F_SETSIG, CSSL_SIGNAL);
        fcntl(serial->fd, F_SETOWN, getpid());
        fcntl(serial->fd, F_SETFL, O_ASYNC | O_NONBLOCK);
    } else {
        /* the read/write operations will be bloking */
        serial->fd = open(fname, O_RDWR | O_NOCTTY);
    }

    /* oops, cannot open */
    if (serial->fd == -1) {
        cssl_error = CSSL_ERROR_OPEN;
        free(serial);
        return nullptr;
    }

    /* we remember old termios */
    tcgetattr(serial->fd, &(serial->oldtio));

    /* now we set new values */
    cssl_setup(serial, baud, parity, bits, stop);

    /* and id */
    serial->id = id;

    /* then set the callback */
    serial->callback = callback;

    /* we add the serial to our list */
    serial->next = head;
    head = serial;

    cssl_error = CSSL_OK;

    return serial;
}


/* closes file, removes serial from the list and frees it */
void cssl_close(cssl_t *serial) {
    cssl_t *cur;

    if (!cssl_started) {
        cssl_error = CSSL_ERROR_NOTSTARTED;
        return;
    }

    if (!serial) {
        cssl_error = CSSL_ERROR_NULLPOINTER;
        return;
    }

    /* first we flush the port */
    tcflush(serial->fd, TCOFLUSH);
    tcflush(serial->fd, TCIFLUSH);

    /* then we restore old settings */
    tcsetattr(serial->fd, TCSANOW, &(serial->oldtio));

    /* and close the file */
    close(serial->fd);

    /* now we can remove the serial from the list */

    if (head == serial) {
        head = serial->next;
        free(serial);
        cssl_error = CSSL_OK;
        return;
    }

    for (cur = head; cur; cur = cur->next) {
        if (cur->next == serial) {
            cur->next = serial->next;
            free(serial);
            cssl_error = CSSL_OK;
            return;
        }
    }

    /* we should never reach there,
       it means, that serial was not found in the list */
    cssl_error = CSSL_ERROR_OOPS;
}


/*-------------------------------------
 * Port setup
 */

/* sets up the port parameters */
void cssl_setup(cssl_t *serial,
                int baud,
                int bits,
                int parity,
                int stop) {
    tcflag_t baudrate;
    tcflag_t databits;
    tcflag_t stopbits;
    tcflag_t checkparity;

    if (!cssl_started) {
        cssl_error = CSSL_ERROR_NOTSTARTED;
        return;
    }

    if (!serial) {
        cssl_error = CSSL_ERROR_NULLPOINTER;
        return;
    }

    /* get the propr baudrate */
    switch (baud) {
        case 75:
            baudrate = B75;
            break;
        case 110:
            baudrate = B110;
            break;
        case 150:
            baudrate = B150;
            break;
        case 300:
            baudrate = B300;
            break;
        case 600:
            baudrate = B600;
            break;
        case 1200:
            baudrate = B1200;
            break;
        case 2400:
            baudrate = B2400;
            break;
        case 4800:
            baudrate = B4800;
            break;
        case 9600:
            baudrate = B9600;
            break;
        case 19200:
            baudrate = B19200;
            break;
        case 38400:
            baudrate = B38400;
            break;
        case 57600:
            baudrate = B57600;
            break;
        case 115200:
            baudrate = B115200;
            break;
        default:
            baudrate = B9600;
    }

    /* databits */
    switch (bits) {
        case 7:
            databits = CS7;
            break;
        case 8:
            databits = CS8;
            break;
        default:
            databits = CS8;
    }

    /* parity, */
    switch (parity) {
        case 0:
            checkparity = 0;
            break;
        case 1:   //odd
            checkparity = PARENB | PARODD;
            break;
        case 2:
            checkparity = PARENB;
            break;
        default:
            checkparity = 0;
    }

    /* and stop bits */
    switch (stop) {
        case 1:
            stopbits = 0;
            break;
        case 2:
            stopbits = CSTOPB;
            break;
        default:
            stopbits = 0;
    }

    /* now we setup the values in port's termios */
    serial->tio.c_cflag = baudrate | databits | checkparity | stopbits | CLOCAL | CREAD;
    serial->tio.c_iflag = IGNPAR;
    serial->tio.c_oflag = 0;
    serial->tio.c_lflag = 0;
    serial->tio.c_cc[VMIN] = 1;
    serial->tio.c_cc[VTIME] = 0;

    /* we flush the port */
    tcflush(serial->fd, TCOFLUSH);
    tcflush(serial->fd, TCIFLUSH);

    /* we send new config to the port */
    tcsetattr(serial->fd, TCSANOW, &(serial->tio));

    cssl_error = CSSL_OK;
}

void cssl_setflowcontrol(cssl_t *serial,
                         int rtscts,
                         int xonxoff) {
    if (!cssl_started) {
        cssl_error = CSSL_ERROR_NOTSTARTED;
        return;
    }

    if (!serial) {
        cssl_error = CSSL_ERROR_NULLPOINTER;
        return;
    }

    /* We setup rts/cts (hardware) flow control */
    if (rtscts) {
        serial->tio.c_cflag |= CRTSCTS;
    } else {
        serial->tio.c_cflag &= ~CRTSCTS;
    }

    /* We setup xon/xoff (soft) flow control */
    if (xonxoff) {
        serial->tio.c_iflag |= (IXON | IXOFF);
    } else {
        serial->tio.c_iflag &= ~(IXON | IXOFF);
    }

    tcsetattr(serial->fd, TCSANOW, &(serial->tio));

    cssl_error = CSSL_OK;
}


/* Blocking mode: sets the timeout in
   hundreds of miliseconds */
void cssl_settimeout(cssl_t *serial, int timeout) {
    if (!cssl_started) {
        cssl_error = CSSL_ERROR_NOTSTARTED;
        return;
    }

    if (!serial) {
        cssl_error = CSSL_ERROR_NULLPOINTER;
        return;
    }

    serial->tio.c_cc[VTIME] = static_cast<cc_t>(timeout);

    tcsetattr(serial->fd, TCSANOW, &(serial->tio));

    cssl_error = CSSL_OK;
}


/*-------------------------------------
 * Serial communication
 */

/* sending a char */
void cssl_putchar(cssl_t *serial,
                  char c) {
    if (!cssl_started) {
        cssl_error = CSSL_ERROR_NOTSTARTED;
        return;
    }

    if (!serial) {
        cssl_error = CSSL_ERROR_NULLPOINTER;
        return;
    }

    write(serial->fd, &c, 1);
}

/* sending a null-terminated string */
void cssl_putstring(cssl_t *serial,
                    char *str) {
    if (!cssl_started) {
        cssl_error = CSSL_ERROR_NOTSTARTED;
        return;
    }

    if (!serial) {
        cssl_error = CSSL_ERROR_NULLPOINTER;
        return;
    }
    write(serial->fd, str, strlen(str));
}


/* sending a data of known size */
void cssl_putdata(cssl_t *serial,
                  uint8_t *data,
                  int datalen) {
    if (!cssl_started) {
        cssl_error = CSSL_ERROR_NOTSTARTED;
        return;
    }

    if (!serial) {
        cssl_error = CSSL_ERROR_NULLPOINTER;
        return;
    }

    write(serial->fd, data, static_cast<size_t>(datalen));
}

void cssl_drain(cssl_t *serial) {
    if (!cssl_started) {
        cssl_error = CSSL_ERROR_NOTSTARTED;
        return;
    }

    if (!serial) {
        cssl_error = CSSL_ERROR_NULLPOINTER;
        return;
    }

    tcdrain(serial->fd);
}

/* blocking mode: reading a char */
int cssl_getchar(cssl_t *serial) {
    int result;
    uint8_t c;

    result = static_cast<int>(read(serial->fd, &c, sizeof(c)));
    if (result <= 0)
        return -1;

    return c;
}

/* blocking mode: reading a data buffer */
int cssl_getdata(cssl_t *serial,
                 uint8_t *buffer,
                 int size) {
    return static_cast<int>(read(serial->fd, buffer, static_cast<size_t>(size)));
}

/*------------------------------------------*/

/* The most important: signal handler */
void cssl_handler(int signo, siginfo_t *info, void *ignored) {
    cssl_t *cur;
    int n;

    /* is this signal which says about
       incoming of the data? */
    if (info->si_code == POLL_IN) {

        /* Yes, we got some data */
        for (cur = head; cur; cur = cur->next) {

            /* Let's find proper cssl_t */
            if (cur->fd == info->si_fd) {

                /* Got it */
                n = static_cast<int>(read(cur->fd, cur->buffer, 255));

                /* Execute callback */
                if ((n > 0) && (cur->callback))
                    cur->callback(cur->id, cur->buffer, n);
                return;
            }
        }
    }
}


